package com.xqboss.apps.service.box;

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xqboss.apps.constant.LockKey;
import com.xqboss.apps.domain.box.BoxNft;
import com.xqboss.apps.mapper.box.BoxNftMapper;
import com.xqboss.common.core.redis.lock.LockValue;
import com.xqboss.common.core.redis.lock.RedisLock;
import com.xqboss.common.exception.ServiceException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
@Service
public class BoxNftService extends ServiceImpl<BoxNftMapper, BoxNft> {


    /**
     * 扣除库存
     *
     * @param boxId
     * @param quantity
     * @return 返回对应的NFT
     */
    @RedisLock(lockKey = LockKey.LOCK_BOX_NFT_STOCK, waitTime = 30)
    @Transactional(rollbackFor = Exception.class)
    public Map<BoxNft, Integer> takeStock(@LockValue Long boxId, Integer quantity, Set<Long> nftIdList) {
        if (Objects.isNull(quantity) || quantity <= 0) {
            throw new ServiceException("操作数量错误！");
        }
        List<BoxNft> boxNftList = list(new LambdaQueryWrapper<BoxNft>()
                .eq(BoxNft::getBoxId, boxId)
                .notIn(nftIdList != null && nftIdList.size() > 0, BoxNft::getNftId, nftIdList)
        );
        int stock = boxNftList.stream().reduce(0, (old, boxNft) -> old + boxNft.getStock(), (a, b) -> null);
        if (stock < quantity) {
            throw new ServiceException("库存数量不足！");
        }

        Map<BoxNft, Integer> result = new HashMap<>();
        // 开箱逻辑： 剩余库存中随机取指定的购买数量
        if (boxNftList.size() == 1) {
            result.put(boxNftList.get(0), quantity);
        } else {
            // 首先，quantity肯定是小范围，因此可以quantity作为基底去随机
            // 随机获取指定范围内【库存】不重复的数字【分配到的商品】

            if (stock > quantity * 10) {
                // 如果库存 远远大于 购买数量 ：采用set防重方式处理
                Set<Integer> indexList = new HashSet<>();
                // 随机出总库存的购买数量的序号，如：A库存3， B库存3，C库存4，（即总库存10）， 购买5个商品， 随机出1、3、5、7、9
                do {
                    int index = RandomUtil.randomInt(1, stock + 1);
                    indexList.add(index);
                } while (indexList.size() < quantity);

                // 转换为每个库存随机出来的数量，接上即可知：A随出2个，B1个，C2个, 第一次循环查询(0-3]序号的数量（2），第二次查询(3-6]序号的数量（1），第三次查询(6-10]的序号数量（2）
                int start = 0;
                for (BoxNft boxNft : boxNftList) {
                    if (boxNft.getStock() > 0) {
                        int finalStart = start;
                        Set<Integer> boxIndexs = indexList.stream().filter(value -> value <= (boxNft.getStock() + finalStart)).collect(Collectors.toSet());

                        if (!boxIndexs.isEmpty()) {
                            indexList.removeAll(boxIndexs);
                            result.put(boxNft, boxIndexs.size());
                        }
                        start += boxNft.getStock();
                    }
                }
            } else {
                // 否则（库存接近购买数量）： 采用洗牌算法
                List<Integer> stockList = new ArrayList<>();
                for (int i = 1; i <= stock; i++) {
                    stockList.add(i);
                }
                Collections.shuffle(stockList);
                List<Integer> indexList = stockList.subList(0, quantity);

                int start = 0;
                for (BoxNft boxNft : boxNftList) {
                    if (boxNft.getStock() > 0) {
                        int finalStart = start;
                        Set<Integer> boxIndexs = indexList.stream().filter(value -> value <= (boxNft.getStock() + finalStart)).collect(Collectors.toSet());

                        if (!boxIndexs.isEmpty()) {
                            indexList.removeAll(boxIndexs);
                            result.put(boxNft, boxIndexs.size());
                        }
                        start += boxNft.getStock();
                    }
                }
            }
        }

        // 扣除绑定库存
        Set<BoxNft> boxNfts = result.keySet();
        for (BoxNft boxNft : boxNfts) {
            // 就只有一个商品就只扣除一个
            Integer count = result.get(boxNft);
            boolean update = update(new LambdaUpdateWrapper<BoxNft>()
                    .setSql("stock=stock-" + count)
                    .eq(BoxNft::getId, boxNft.getId())
                    .ge(BoxNft::getStock, count)
            );
            if (!update) {
                throw new ServiceException("扣除库存失败！");
            }
        }

        return result;
    }

}
